package Lex;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class LexAnalyzer {
    private final List<String> keyWords = Arrays.asList(
            "begin", "end", "if", "then", "else", "const", "procedure", "var", "do", "while", "call", "read", "write", "odd", "repeat", "until"
    );
    private List<Token> allToken = null;
    File file;

    // 字符读取操作相关
    char ch;
    String line;
    int pos;
    int lineLength;

    public LexAnalyzer(File file) {
        this.file = file;
        analysis();
        saveAllToken();
    }

    public void analysis() {
        allToken = new ArrayList<>();
        try {
            try (FileInputStream fis = new FileInputStream(file);
                 InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
                 BufferedReader br = new BufferedReader(isr)
            ) {
                int lineNum = 0;
                while ((line = br.readLine()) != null) {
                    // 相关值初始化
                    lineLength = line.length();
                    pos = 0;
                    ch = ' ';
                    lineNum++;

                    // 开始解析当前行
                    while (pos < lineLength) {
                        getChar();

                        // 进行关键字和标识符的识别
                        if (isLetter(ch)) {
                            // 读取单词
                            StringBuilder tmp = new StringBuilder();
                            while (isLetter(ch) || isDigit(ch)) {
                                tmp.append(ch);
                                getChar();
                            }
                            backStep();

                            // 关键字判断，若是关键字则将关键字加入token，否则加入标识符
                            if (!isKeyWord(tmp.toString(), lineNum)) {
                                // 说明只是一个变量名
                                allToken.add(new Token(Symbol.SYM, lineNum, tmp.toString()));
                            }
                        }

                        // 数字判断，只有整型
                        else if (isDigit(ch)) {
                            StringBuilder tmp = new StringBuilder();
                            while (isDigit(ch) || isLetter(ch)) {
                                tmp.append(ch);
                                getChar();
                            }
                            backStep();
                            allToken.add(new Token(Symbol.CONST, lineNum, tmp.toString()));
                        }

                        // 简单运算符判断
                        else if (ch == '+') allToken.add(new Token(Symbol.ADD, lineNum, "_"));
                        else if (ch == '-') allToken.add(new Token(Symbol.SUB, lineNum, "_"));
                        else if (ch == '*') allToken.add(new Token(Symbol.MUL, lineNum, "_"));
                        else if (ch == '/') allToken.add(new Token(Symbol.DIV, lineNum, "_"));
                        else if (ch == ';') allToken.add(new Token(Symbol.SEMIC, lineNum, "_"));
                        else if (ch == ',') allToken.add(new Token(Symbol.COMMA, lineNum, "_"));
                        else if (ch == '.') allToken.add(new Token(Symbol.POI, lineNum, "_"));
                        else if (ch == '(') allToken.add(new Token(Symbol.LBR, lineNum, "_"));
                        else if (ch == ')') allToken.add(new Token(Symbol.RBR, lineNum, "_"));
                        else if (ch == '=') allToken.add(new Token(Symbol.EQU, lineNum, "_"));

                            // 复杂运算符判断
                        else if (ch == ':') {
                            getChar();
                            if (ch == '=') {
                                allToken.add(new Token(Symbol.CEQU, lineNum, "_"));
                            } else {
                                allToken.add(new Token(Symbol.COL, lineNum, "_"));
                                backStep();
                            }
                        } else if (ch == '<') {
                            getChar();
                            if (ch == '=') {
                                allToken.add(new Token(Symbol.LESE, lineNum, "_"));
                            } else if (ch == '>') {
                                allToken.add(new Token(Symbol.NEQU, lineNum, "_"));
                            } else {
                                allToken.add(new Token(Symbol.LES, lineNum, "_"));
                                backStep();
                            }
                        } else if (ch == '>') {
                            getChar();
                            if (ch == '=') {
                                allToken.add(new Token(Symbol.LARE, lineNum, "_"));
                            } else {
                                backStep();
                                allToken.add(new Token(Symbol.LAR, lineNum, "_"));
                            }
                        }
                    }

                }

                allToken.add(new Token(Symbol.EOF, lineNum, "_"));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public boolean isLetter(char ch) {
        if (ch >= 'a' && ch <= 'z') {
            return true;
        } else return ch >= 'A' && ch <= 'Z';
    }

    public boolean isDigit(char ch) {
        return ch <= '9' && ch >= '0';
    }

    public boolean isKeyWord(String s, int lineNum) {
        for (int i = 0; i < keyWords.size(); i++) {
            if (s.equals(keyWords.get(i))) {
                allToken.add(new Token(Symbol.values()[i], lineNum, "_"));
                return true;
            }
        }
        return false;
    }

    public void getChar() {
        if (pos < lineLength) {
            ch = line.charAt(pos);
        } else {
            ch = '$';
        }
        pos++;
    }

    public void backStep() {
        if (ch != '$') {
            pos--;
            ch = ' ';
        }
    }

    public List<Token> getAllToken() {
        if (allToken == null) analysis();
        return allToken;
    }

    public void saveAllToken() {
        File f = new File("resources/res_token_" + file.getName());
        if (f.exists()) {
            f.delete();
        }
        try {
            f.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        PrintStream stream = null;
        try {

            stream = new PrintStream(f);//写入的文件path
            for (Token token : allToken) {
                stream.println(token.toString());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
